package gin

import (
	"fmt"
	"reflect"
	"strings"
	"sync"
	"time"

	"gitee.com/zacyuan/yuan/pkg/share"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

type HandlerFunc = gin.HandlerFunc

var (
	pCtx = sync.Pool{
		New: func() any {
			return new(Context)
		},
	}
)

type Code interface {
	Code() int64
}

type Handler func(*Context) (any, error)

type Return struct {
	Ret       int64  `json:"ret"`
	Msg       string `json:"msg"`
	Data      any    `json:"data,omitempty"`
	RequestID string `json:"request_id,omitempty"`
	Timestamp int64  `json:"timestamp,omitempty"`
}

func handle(ctl interface{}, method string, hooks ...Hook) func(*gin.Context) {
	main := reflect.ValueOf(ctl).MethodByName(method)
	fn := func(ctx *Context) (any, error) {
		if !main.IsValid() {
			return nil, fmt.Errorf("%s方法不存在。", method)
		}

		rets := main.Call([]reflect.Value{reflect.ValueOf(ctx)})
		if len(rets) != 2 {
			return nil, fmt.Errorf("%s方法格式不正确。", method)
		}

		if !rets[1].IsNil() {
			err, ok := rets[1].Interface().(error)
			if !ok {
				return nil, fmt.Errorf("%s方法格式不正确。", method)
			}
			return nil, err
		}

		return rets[0].Interface(), nil
	}

	for i := len(hooks) - 1; i >= 0; i-- {
		fn = hooks[i](fn)
	}

	return func(c *gin.Context) {
		ctx := pCtx.Get().(*Context)
		ctx.Context = c

		requestID := ctx.GetString(RequestIDKey)
		if requestID == "" {
			requestID = uuid.NewString()
			ctx.Set(RequestIDKey, requestID)
		}
		ret := &Return{
			Ret:       0,
			Msg:       "ok",
			RequestID: requestID,
		}

		data, err := fn(&Context{Context: c})
		ret.Timestamp = time.Now().UnixMilli()
		if err != nil {
			ret.Ret = -999
			ret.Msg = err.Error()
			if c, ok := err.(Code); ok {
				ret.Ret = c.Code()
			}
		}

		ret.Data = data
		ctx.RenderJSON(ret)
	}
}

func RegisterController(router IRoutes, ctls []any, hooks ...Hook) {
	for _, one := range ctls {
		rt := reflect.TypeOf(one)
		tmp := strings.Split(rt.String(), ".")
		name := tmp[len(tmp)-1]

		count := rt.NumMethod()
		for i := 0; i < count; i++ {
			method := rt.Method(i)
			uri := share.SnakeString(name) + "/" + share.SnakeString(method.Name)
			router.GET(uri, handle(one, method.Name, hooks...))
			router.POST(uri, handle(one, method.Name, hooks...))
		}
	}
}
